/**
 * TransferUtils.java
 */
package fss.transfer;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import fss.util.FssUtils;
import fss.util.SerializableUtils;

/**
 * Some utility methods involving tranfers.
 * 
 * @author thinh
 *
 */
public class TransferUtils {
    /**
     * A normalized utility method to convert a transfer identifier into
     * a filename to save a transfer.
     * @param dir the directory (omitting the last '/').
     * @param transferId the transfer identifier.
     * @return the filename.
     */
    public static String getTransferFilename(String dir, TransferId transferId) {
        StringBuilder filename = new StringBuilder();
        filename.append(dir != null ? dir + FssUtils.getSystemFileSeparator() : "");
        filename.append(transferId.getType() + "_");
        filename.append(transferId.getFilename());
        filename.append(".incomplete");
        return filename.toString();
    }
    /**
     * A normalized utility method to convert a transfer identifier into
     * a filename to save a transfer.
     * @param transferId the transfer identifier.
     * @return the filename.
     */
    public static String getTransferFilename(TransferId transferId) {
        return getTransferFilename(null, transferId);
    }
    /**
     * Given the original transfer (source), update the transfer with the 
     * received byte array from another transfer chunk data.
     * @param source the source transfer.
     * @param receivedData the received transfer chunk data.
     */
    public static void combineTransferData(Transfer source, byte[] receivedData) {
        byte[] sourceData = source.getData();
        byte[] combinedData = new byte[sourceData.length + receivedData.length];
        
        int index = 0;
        for(byte b : sourceData) {
            combinedData[index++] = b;
        }
        for(byte b : receivedData) {
            combinedData[index++] = b;
        }
        source.setData(combinedData);
    }
    /**
     * Given the original transfer (source), update the transfer with the received
     * transfer.
     * @param source the source transfer.
     * @param received the received transfer.
     */
    public static void combineTransfer(Transfer source, Transfer received) {
        source.setStatus(received.getStatus());
        source.setOffset(received.getOffset());
        source.setLastUpdateTimestamp(received.getLastUpdateTimestamp());
        source.setSequence(received.getSequence());
        combineTransferData(source, received.getData());
    }
    /**
     * Unmarshall a transfer object into a file.
     * <p>
     * This is assuming that the passed in transfer is completed and the data
     * array contains all the necessary data to write to file.
     * @param completedTransfer the complete transfer object.
     * @param dir the directory (not the filename) to store the data.
     * @throws IOException error converting to file.
     */
    public static void convertTransferToFile(Transfer completedTransfer, String dir) throws IOException {
        String filename = (dir != null ? dir + FssUtils.getSystemFileSeparator() : "") + completedTransfer.getId().getFilename();
        FileOutputStream fos = null;
        ByteArrayOutputStream bos = null;
        File file = null;
        try {
            file = new File(filename);
            
            bos = new ByteArrayOutputStream();
            bos.write(completedTransfer.getData());
            
            fos = new FileOutputStream(file);
            bos.writeTo(fos);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null) fos.close();
            if(bos != null) bos.close();
        }
    }
    /**
     * Given a directory, find all the file(s) ending with ".incomplete" and serialize them
     * into a Transfer object.
     * <p>
     * The assumption is that all files ending in ".incomplete" are of type <code>Transfer</code>
     * when deserialized into object form.
     * @param dir the root directory of data.
     * @return a list of transfer objects or an empty list.
     */
    public static List<Transfer> getIncompleteTransferList(String dir) {
        List<Transfer> results = new ArrayList<Transfer>();
        File directory = new File(dir);
        if(directory.exists()) {
            for(File file : directory.listFiles()) {
                if(file.getAbsolutePath().endsWith(".incomplete")) {
                    Transfer transfer = (Transfer) SerializableUtils.deserializeObject(file.getAbsolutePath());
                    results.add(transfer);
                }
            }
        }
        return results;
    }
    /**
     * Given a directory, find all the file(s) ending with ".incomplete" and delete them.
     * @param dir the root directory of data.
     */
    public static void removeAllIncompleteTransfers(String dir) {
        File directory = new File(dir);
        if(directory.exists()) {
            for(File file : directory.listFiles()) {
                if(file.getName().contains(".incomplete")) {
                    file.delete();
                }
            }
        }
    }
    /**
     * Logic to handle a transfer received from either client or service.
     * @param logger the client/service logger used.
     * @param datadir the data directory.
     * @param transfer the transfer object.
     */
    public static void handleTransfer(Logger logger, String datadir, Transfer transfer) {
        // only process if this client is the intended destination
        String persistedFilename = datadir + FssUtils.getSystemFileSeparator() + TransferUtils.getTransferFilename(transfer.getId());
        
        File transferFile = new File(persistedFilename);
        // this is the first received chunk, just serialize it
        if(!transferFile.exists()) {
            SerializableUtils.serializeObject(transfer, persistedFilename);
            logger.info("Initial Transfer serialized to " + persistedFilename);
        } 
        // processing the transfer chunk
        else {
            Transfer previousTransfer = (Transfer) SerializableUtils.deserializeObject(persistedFilename);
            logger.info("\nPrevious Transfer: " + previousTransfer + "\nReceived Transfer: " + transfer);
            
            if(transfer.getStatus() == StatusType.COMPLETED) {
                logger.info(transfer.getId().getType().getDisplayValue() + " transfer is complete. Removing serialized chunk " + 
                        persistedFilename + " (" + FssUtils.removeFile(persistedFilename) + ")");
                try {
                    TransferUtils.convertTransferToFile(previousTransfer, datadir);
                } catch (IOException e) {
                    e.printStackTrace();
                    logger.warning("An error occurred but if it is an EOFExcpetion, then it can be ignored.");
                }
            } else {
                // update the previous transfer chunk with the updated 
                TransferUtils.combineTransfer(previousTransfer, transfer);

                // serialize the file over the old file
                SerializableUtils.serializeObject(previousTransfer, persistedFilename);
            }
        }
    }
}
